#include <memory>

#include "plc2llvm/Visitor/strategy/pou_decl/pou_decl.h"
#include "plc2llvm/ScopeSystem/ScopeManager.h"
#include "plc2llvm/ObjectSystem/Object.h"
#include "plc2llvm/Semantic/SemanticError.h"
#include "plc2llvm/TypeSystem/POU/PROGType.h"
#include "plc2llvm/Visitor/Visitor.h"

#define ANY_CAST_TO_OBJ(x) std::any_cast<std::shared_ptr<plcst::Object>>(x)

namespace pou_decl
{

    std::any visitStartpoint(plcst::PLCSTParser::StartpointContext* ctx, Visitor* visitor) {
        for(auto pou : ctx->pOU_Decl()){
            visitor->visit(pou);
        }

        // collect prog objs
        std::vector<llvm::Function*> prog_list;
        for(auto prog : ctx->prog_Decl()){
            auto raw_prog_obj = visitor->visit(prog);
            auto prog_obj = ANY_CAST_TO_OBJ(raw_prog_obj);
            auto raw_prog_value = prog_obj->llvmval;
            auto prog_value = llvm::cast<llvm::Function>(raw_prog_value);
            prog_list.push_back(prog_value);
        }

        for(auto scf : ctx->sFC()){
            visitor->visit(scf);
        }

        for(auto config_decl : ctx->config_Decl()){
            visitor->visit(config_decl);
        }

        // codegen
        auto functionType = llvm::FunctionType::get(visitor->builder->getInt32Ty(), false);
        auto main_func = llvm::Function::Create(functionType, llvm::GlobalValue::ExternalLinkage, "main", visitor->module);
        auto entry = llvm::BasicBlock::Create(visitor->context, "main.entry", main_func);
        visitor->builder->SetInsertPoint(entry);
        //  call progs
        for(auto prog_func : prog_list){
            visitor->builder->CreateCall(prog_func);
        }
        visitor->builder->CreateRet(visitor->builder->getInt32(0));
        llvm::verifyFunction(*main_func);
        return nullptr;
    }


    std::any visitFunc_Decl(plcst::PLCSTParser::Func_DeclContext *ctx, Visitor *visitor)
    {
        return visitor->visitChildren(ctx);
    }

    std::any visitProg_Decl(plcst::PLCSTParser::Prog_DeclContext *ctx, Visitor *visitor)
    {
        auto name = ctx->prog_Type_Name()->getText();
        // codegen
        auto functionType = llvm::FunctionType::get(visitor->builder->getVoidTy(), false);
        auto plc_prg = llvm::Function::Create(functionType, llvm::GlobalValue::ExternalLinkage, name, visitor->module);
        //--------------------------------------------------------------------------------------

        auto currentScope = plcst::ScopeManager::getScopeManager().getCurrentScope();

        // new type and obj
        std::shared_ptr<plcst::PROGType> &&newProgType = std::make_shared<plcst::PROGType>(std::string(name));
        std::shared_ptr<plcst::Object> newProgObj = std::make_shared<plcst::Object>(newProgType, std::move(name));
        newProgObj->llvmval = plc_prg;

        currentScope->insert<plcst::Object>(newProgObj);
        // new scope
        auto newScope = std::make_unique<plcst::Scope>(currentScope, newProgObj);
        plcst::ScopeManager::getScopeManager().addNewScope(std::move(newScope));

        // codegen
        auto entry = llvm::BasicBlock::Create(visitor->context, "entry", plc_prg);
        visitor->builder->SetInsertPoint(entry);
        //--------------------------------------------------------------------------------------

        // 访问变量声明段，变量的加表过程放在访问过程中，故忽略返回值
        // 访问第3个到倒数第三个
        auto &childrenVector = ctx->children;
        for (int i = 2; i < childrenVector.size() - 2; i++)
        {
            visitor->visit(childrenVector[i]);
        }

        // 访问fb_body
        visitor->visit(ctx->fB_Body());

        // 访问结束，作用域出栈
        plcst::ScopeManager::getScopeManager().deleteCurrentScope();

        // codegen
        //--------------------------------------------------------------------------------------
        visitor->builder->CreateRetVoid();
        llvm::verifyFunction(*plc_prg);
        return newProgObj;
    }

    std::any visitProg_Type_Name(plcst::PLCSTParser::Prog_Type_NameContext *ctx, Visitor *visitor)
    {
        return visitor->visit(ctx->identifier());
    }

}